/**
 * @author Justin Akin
 * @version Action Pals Core Library 0.1.0
 */
package com.actionpals.graphics
{
	import com.actionpals.base.BaseSprite;
	
	import flash.display.DisplayObject;
	import flash.events.Event;
	import flash.geom.Rectangle;
	
	public class AbstractShape extends BaseSprite
	{
		
	//--------------------------------------------------------------------------------
	//
	//	Protected Member Variables
	//
	//--------------------------------------------------------------------------------
	
		/**
		 * @private
		 */		
		protected var _invalid:Boolean;
	
		/**
		 * @private
		 */		
		protected var _width:Number;
	
		/**
		 * @private
		 */		
		protected var _height:Number;
	
		/**
		 * @private
		 */		
		protected var _fillColor:uint;
	
		/**
		 * @private
		 */		
		protected var _fillAlpha:Number;
	
		/**
		 * @private
		 */		
		protected var _fillGradient:Gradient;
	
		/**
		 * @private
		 */		
		protected var _strokeThickness:uint;
	
		/**
		 * @private
		 */		
		protected var _strokeColor:uint;
	
		/**
		 * @private
		 */		
		protected var _strokeAlpha:Number;
	
		/**
		 * @private
		 */		
		protected var _strokeGradient:Gradient;
		
	//--------------------------------------------------------------------------------
	//
	//	Constructor
	//
	//--------------------------------------------------------------------------------
		
		/**
		 * Constructs the basic parts of a Shape.
		 * 
		 * @param width
		 * @param height
		 * @param fillColor
		 * @param strokeThickness
		 * @param strokeColor
		 */		
		public function AbstractShape
		(
			width:Number,
			height:Number,
			fillColor:uint = 0,
			strokeThickness:uint = 0,
			strokeColor:uint = 0
		)
		{
			super();
			
			_width = width;
			_height = height;
			_fillColor = fillColor;
			_strokeThickness = strokeThickness;
			_strokeColor = strokeColor;
			_fillAlpha = 1;
			_strokeAlpha = 1;
			
			init();
		}
		
		/**
		 * Forces the draw method to be called immediately vs waiting for an Event.RENDER event
		 * to come back.  Using this method helps solve issues where you need more control.
		 * For example when you set the height and width properties you can access them and
		 * recieve the correct values but if the shape is contained in a parent object that object
		 * will not have the new width reflected in it's own width.  Using drawNow() when you need
		 * it still accomplishes the same goal of limiting the number of times draw is called.
		 */		
		public function drawNow():void
		{
			_invalid = false;
			draw();
		}
		
		/**
		 * @inheritDoc
		 */		
		public override function getRect( targetCoordinateSpace:DisplayObject ):Rectangle
		{
			var rect:Rectangle = super.getRect( targetCoordinateSpace );
			return new Rectangle( rect.x, rect.y, this.width, this.height );
		}
		
		/**
		 * @inheritDoc
		 */	
		public override function getBounds( targetCoordinateSpace:DisplayObject ):Rectangle
		{
			var rect:Rectangle = super.getRect( targetCoordinateSpace );
			return new Rectangle( rect.x, rect.y, this.width, this.height );
		}
		
	//--------------------------------------------------------------------------------
	//
	//	Getters and Setters
	//
	//--------------------------------------------------------------------------------
				
		/**
		 * @inheritDoc
		 */	
		public override function get width():Number
		{
			return _width;
		}
		
		/**
		 * @private
		 */		
		public override function set width( value:Number ):void
		{
			if( _width == value ) return;
			_width = value;
			invalidate();
		}
		
		/**
		 * @inheritDoc
		 */	
		public override function get height():Number
		{
			return _height;
		}
		
		/**
		 * @private
		 */	
		public override function set height( value:Number ):void
		{
			if( _height == value ) return;
			_height = value;
			invalidate();
		}
		
		/**
		 * The color value of the fill area.  This will not be reflected in the
		 * drawn shape unless the fillGradient value is null.
		 * 
		 * @return 
		 */		
		public function get fillColor():uint
		{
			return _fillColor;
		}
		
		/**
		 * @private
		 */	
		public function set fillColor( value:uint ):void
		{
			if( _fillColor == value ) return;
			_fillColor = value;
			invalidate();
		}
		
		/**
		 * The alpha value of the fill area.  This will not be reflected in the
		 * drawn shape unless the fillGradient value is null.
		 * 
		 * @return 
		 */		
		public function get fillAlpha():Number
		{
			return _fillAlpha;
		}
		
		/**
		 * @private
		 */	
		public function set fillAlpha( value:Number ):void
		{			
			value = ( value > 1 ) ? 1 : value;
			value = ( value < 0 ) ? 0 : value;
			
			if( _fillAlpha == value ) return;
			_fillAlpha = value;
			invalidate();
		}
		
		/**
		 * The Gradient object associated with drawing the fill area.  If this 
		 * value is present then the fillColor and fillAlpha properties will
		 * not be reflected in the final draw.
		 * 
		 * @return 
		 */		
		public function get fillGradient():Gradient
		{
			return _fillGradient;
		}
		
		/**
		 * @private
		 */	
		public function set fillGradient( value:Gradient ):void
		{
			if( _fillGradient == value ) return;
			_fillGradient = value;
			_fillGradient.addEventListener( Event.CHANGE, onGradientChange );
			invalidate();
			
		}
		
		/**
		 * The thickness of the stroke around the fill.  For example a thickness of 2 would
		 * create a 2px border all the way around you fill.  Unlike flash strokes these
		 * strokes are also fills.  When they are drawn they are always drawn within the
		 * current width.  They do not extend out and also overlap the fill.  This makes
		 * math much easier and gives you a cleaner stroke.
		 * 
		 * @return 
		 */		
		public function get strokeThickness():uint
		{
			return _strokeThickness;
		}
		
		/**
		 * @private
		 */	
		public function set strokeThickness( value:uint ):void
		{
			if( _strokeThickness == value ) return;
			_strokeThickness = value;
			invalidate();
		}
		
		/**
		 * The color value of the stroke.  This will not be reflected in the
		 * drawn shape unless the strokeGradient value is null.
		 * 
		 * @return 
		 */		
		public function get strokeColor():uint
		{
			return _strokeColor;
		}
		
		/**
		 * @private
		 */	
		public function set strokeColor( value:uint ):void
		{
			if( _strokeColor == value ) return;
			_strokeColor = value;
			invalidate();
		}
		
		/**
		 * The alpha value of the stroke.  This will not be reflected in the
		 * drawn shape unless the strokeGradient value is null.
		 * 
		 * @return 
		 */	
		public function get strokeAlpha():Number
		{
			return _strokeAlpha;
		}
		
		/**
		 * @private
		 */	
		public function set strokeAlpha( value:Number ):void
		{
			value = ( value > 1 ) ? 1 : value;
			value = ( value < 0 ) ? 0 : value;
			
			if( _strokeAlpha == value ) return;
			_strokeAlpha = value;
			invalidate();
		}
		
		/**
		 * The Gradient object associated with drawing the stroke.  If this 
		 * value is present then the strokeColor and strokeAlpha properties will
		 * not be reflected in the final draw.
		 * 
		 * @return 
		 */		
		public function get strokeGradient():Gradient
		{
			return _strokeGradient;
		}
		
		/**
		 * @private
		 */	
		public function set strokeGradient( value:Gradient ):void
		{
			if( _strokeGradient == value ) return;
			_strokeGradient = value;
			_strokeGradient.addEventListener( Event.CHANGE, onGradientChange );
			invalidate();
		}
		
		/**
		 * @private
		 */		
		protected function get fillWidth():Number
		{
			return _width - ( _strokeThickness * 2 );
		}
		
		/**
		 * @private
		 */	
		protected function get fillHeight():Number
		{
			return _height - ( _strokeThickness * 2 );
		}
		
		
	//--------------------------------------------------------------------------------
	//
	//	Protected Methods
	//
	//--------------------------------------------------------------------------------
		
		/**
		 * @private
		 */	
		protected function init():void
		{
			addListeners();
			draw();
		}
		
		/**
		 * @private
		 */	
		protected function addListeners():void
		{
			addEventListener( Event.ADDED_TO_STAGE, onAddedToStage );
			addEventListener( Event.REMOVED_FROM_STAGE, onRemovedFromStage );
		}
		
		/**
		 * @private
		 */	
		protected function invalidate():void
		{
			if( !_invalid )
			{
				try
				{
					stage.invalidate();
					_invalid = true;
				}
				catch( error:Error )
				{
					_invalid = false;
				}
			}
		}
		
		/**
		 * @private
		 */	
		protected function applyGradient( gradient:Gradient ):void
		{
			graphics.beginGradientFill(	gradient.type,
										gradient.colors,
										gradient.alphas,
										gradient.ratios,
										gradient.matrix,
										gradient.spreadMethod,
										gradient.interpolationMethod,
										gradient.focalPointRatio )
		}
		
		
		/**
		 * @private
		 */	
		protected function draw():void
		{
		}
		
		
		/**
		 * @private
		 */	
		protected function destroy():void
		{
			graphics.clear();
		}
		
		/**
		 * @private
		 */	
		protected function onGradientChange( event:Event ):void
		{
			invalidate();
		}
		
		/**
		 * @private
		 */	
		protected function onRender( event:Event ):void
		{
			if( _invalid )
			{
				draw();
				_invalid = false;
			}
		}	
		
		/**
		 * @private
		 */	
		protected function onAddedToStage( event:Event ):void
		{
			stage.addEventListener( Event.RENDER, onRender );
			draw();
		}
		
		/**
		 * @private
		 */	
		protected function onRemovedFromStage( event:Event ):void
		{
			removeEventListener( Event.ADDED_TO_STAGE, onAddedToStage );
			removeEventListener( Event.REMOVED_FROM_STAGE, onRemovedFromStage );
		}
	}
}